package com.lcc.bank.tools.netty;

import com.lcc.bank.tools.util.config.Config;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.nio.charset.Charset;

/**
 * @author lincc
 * @version 1.0  2019/2/22
 */
@Slf4j
@Service
public class NettyServer extends Thread {

    private static boolean STARTED = false;

    @Override
    public void run() {
        // 创建用于监听accept的线程池
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 创建用于处理队列和数据的线程池
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // netty服务器的创建, 辅助工具类，用于服务器通道的一系列配置
            ServerBootstrap bootstrap = new ServerBootstrap();

            // BACKLOG用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时
            // ，用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1，Java将使用默认值50。
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);

            // SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口，
            // 即使以前建立的将此端口用做他们的本地端口的连接仍存在。
            // 这通常是重启监听服务器时出现，若不设置此选项，则bind时将出错。
            // SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例，
            // 只要每个实例捆绑一个不同的本地IP地址即可。对于TCP，
            // 我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
            // SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上，
            // 只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
            // SO_REUSEADDR允许完全重复的捆绑：当一个IP地址和端口绑定到某个套接口上时，
            // 还允许此IP地址和端口捆绑到另一个套接口上。一般来说，这个特性仅在支持多播的系统上才有，
            // 而且只对UDP套接口而言（TCP不支持多播）
            bootstrap.option(ChannelOption.SO_REUSEADDR, true);

            // Netty4使用对象池，重用缓冲区
            bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            // 绑定线程池
            bootstrap.group(group, bossGroup);
            // 指定使用的channel
            bootstrap.channel(NioServerSocketChannel.class);
            // 绑定监听端口
            ;
            // 绑定客户端连接时候触发操作
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    log.info("信息：有一客户端链接到本服务端;IP:" + ch.localAddress().getAddress() + "，Port:" + ch.localAddress().getPort());
                    //设置读取格式
                    ch.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
                    // 客户端触发操作
                    ch.pipeline().addLast(new NettyServerHandler());
                    ch.pipeline().addLast(new ByteArrayEncoder());
                }
            });
            // 服务器异步创建绑定
            ChannelFuture cf = bootstrap.bind(Config.getNettyIp(), Config.getNettyPort()).sync();
            log.info(NettyServer.class + " 启动正在监听： " + cf.channel().localAddress());
            // 关闭服务器通道
            cf.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放线程池资源
            group.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public void init() {
        if (STARTED) {
            throw new RuntimeException("用户重复启动监听。");
        } else {
            STARTED = true;
        }
        log.debug("监听接受报文。");
        this.start();
    }
}
